package uk.co.q3c.deplan.client.domain.task;

import java.util.ArrayList;
import java.util.Date;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import uk.co.q3c.deplan.client.domain.resource.PersistenceReferenceException;
import uk.co.q3c.deplan.client.service.ServiceFactory;

/**
 * Gathers status information from tasks, usually to report on results of levelling
 * 
 * @author DSowerby 31 Jul 2008 22:54:06
 * 
 */
public class TaskStatusReport {
	protected static final Logger logger = Logger.getLogger(TaskStatusReport.class.getName());
	Map<TaskAnalysisResult, List<Task>> results = new EnumMap<TaskAnalysisResult, List<Task>>(TaskAnalysisResult.class);
	private Date levelDate;

	/**
	 * Constructs the map of TaskAnalysisResult to value
	 */
	public TaskStatusReport() {
		for (TaskAnalysisResult tar : TaskAnalysisResult.values()) {
			results.put(tar, new ArrayList<Task>());
		}
	}

	public void analyse(Task task, ServiceFactory connection) throws PersistenceReferenceException {
		inc(TaskAnalysisResult.ALLTASKS, task);
		typeCount(task);
		complete(task);
		atomic(task, connection);
		planning(task, connection);
	}

	/**
	 * Analyses planning status. Ignores summary tasks and completed tasks, but then counts each of the following: <br>
	 * <li>tasks which have planned start dates before the level date. If this returns a value other than 0 after levelling, there is an
	 * error in the levelling process. <li>tasks which are not planned (task.getStartRemaining() == null). <li>resourced tasks which have no
	 * resource assigned <li>resourced tasks which have resource assigned but 0 assignment. <li>resourced tasks which have a resource group
	 * assigned, but no resources within the group
	 * 
	 * @throws PersistenceReferenceException
	 */
	private void planning(Task task, ServiceFactory connection) throws PersistenceReferenceException {

		if (!connection.taskService()
				.taskIsAtomic(task)) {
			return;
		}

		if (task.isComplete()) {
			inc(TaskAnalysisResult.ATOMIC_COMPLETE, task);
			return;
		}
		if (task.isResourceDriven()) {
			BaseTask rt = (BaseTask) task;
			if (rt.getAssignedResource() == null) {
				inc(TaskAnalysisResult.NOT_RESOURCED, task);
				return;
			}
			if (rt.assignedUnits <= 0) {
				inc(TaskAnalysisResult.ZERO_ASSIGNMENT, task);
				return;
			}
			if (rt.getAssignedResource()
					.count() == 0) {
				inc(TaskAnalysisResult.RESOURCE_GROUP_EMPTY, task);
				return;
			}
		}
		if (task.getStartRemaining() == null) {
			inc(TaskAnalysisResult.NOT_PLANNED, task);
			return;
		}

		// because of the limitation (see
		// http://deplan.wiki.sourceforge.net/Levelling#limitation) comparison
		// can
		// only be on date not time
		Date diLevel = new Date(levelDate.getTime());
		Date diSR = new Date(task.getStartRemaining()
				.getTime());

		if (diSR.compareTo(diLevel) < 0) {
			inc(TaskAnalysisResult.PLANNED_TOO_EARLY, task);
			return;

		}
		// use this when limitation fixed
		// if (task.getStartRemaining().compareTo(levelDate) < 0) {
		// inc(TaskAnalysisResult.PLANNED_TOO_EARLY, task);
		// return;
		// }
		inc(TaskAnalysisResult.PLANNED, task);
	}

	private void atomic(Task task, ServiceFactory connection) throws PersistenceReferenceException {
		if (connection.taskService()
				.taskIsAtomic(task)) {
			inc(TaskAnalysisResult.ATOMIC, task);
		}
	}

	private void complete(Task task) {
		if (task.isComplete()) {
			inc(TaskAnalysisResult.COMPLETED, task);
		}
	}

	private void typeCount(Task task) {
		throw new RuntimeException("not yet implemented");
		// int type = task.getClassId();
		// switch (type) {
		// case Dao.CLASSID_PROJECT:
		// inc(TaskAnalysisResult.PROJECTS, task);
		// break;
		// case Dao.CLASSID_AbstractTask:
		// inc(TaskAnalysisResult.PROJECTSTASKS, task);
		// break;
		// case Dao.CLASSID_PROJECTSUMMARYTASK:
		// inc(TaskAnalysisResult.SUMMARIES, task);
		// break;
		// case Dao.CLASSID_CONTINGENCYTASK:
		// inc(TaskAnalysisResult.CONTINGENCIES, task);
		// break;
		//
		// case Dao.CLASSID_SERVICE:
		// inc(TaskAnalysisResult.SUPPORTSERVICES, task);
		// break;
		// case Dao.CLASSID_SUPPORTTASK:
		// inc(TaskAnalysisResult.SUPPORTTASKS, task);
		// break;
		// default:
		// break;
		// }
	}

	private void inc(TaskAnalysisResult tar, Task task) {
		results.get(tar)
				.add(task);
	}

	public Date getLevelDate() {
		return levelDate;
	}

	public void setLevelDate(Date levelDate) {
		this.levelDate = levelDate;
	}

	public void output() {
		for (TaskAnalysisResult tar : TaskAnalysisResult.values()) {
			logger.info(tar.toString() + " " + resultLine(tar));
		}

	}

	public void output(StringBuffer s) {
		for (TaskAnalysisResult tar : TaskAnalysisResult.values()) {
			s.append(tar.toString() + " " + resultLine(tar));
		}

	}

	public void output(StringBuffer s, int style) {
		switch (style) {
		case 0:
			levellingReport(s);
			break;
		}
	}

	private void levellingReport(StringBuffer s) {
		int totalTasks = resultCount(TaskAnalysisResult.ALLTASKS);
		int Tasks = resultCount(TaskAnalysisResult.ATOMIC);
		int rolledTasks = totalTasks - Tasks;
		int plannedTasks = resultCount(TaskAnalysisResult.PLANNED);
		int completedTasks = resultCount(TaskAnalysisResult.ATOMIC_COMPLETE);
		int unplannedTasks = Tasks - completedTasks - plannedTasks;

		s.append("LEVELLING REPORT\n");
		s.append("\nTotal tasks: \t\t" + totalTasks + "\n\n");
		s.append("Summary tasks: \t" + rolledTasks + " (these don't require levelling)\n");
		s.append("\t Projects: " + resultLine(TaskAnalysisResult.PROJECTS));
		s.append("\t Summaries: " + resultLine(TaskAnalysisResult.SUMMARIES));
		s.append("\t Services: " + resultLine(TaskAnalysisResult.SUPPORTSERVICES));
		s.append("\nAtomic tasks: \t" + Tasks + " (these should be levelled)\n");
		s.append("\t Project tasks: " + resultLine(TaskAnalysisResult.PROJECTSTASKS));
		s.append("\t Support tasks: " + resultLine(TaskAnalysisResult.SUPPORTTASKS));
		s.append("\t Contingency tasks: " + resultLine(TaskAnalysisResult.CONTINGENCIES));
		s.append("\n Planning status\n");
		s.append("\t Completed: \t" + completedTasks + "\n");
		s.append("\t Planned: \t" + plannedTasks + "\n");
		s.append("\n\n == The following section is relevant only after levelling ==\n\n");
		s.append(" Planning discrepancies\n\n");
		s.append(" Of " + Tasks + " atomic tasks which should be planned or complete " + unplannedTasks + " are not\n\n");
		s.append("\t No resource assigned: \t" + resultLine(TaskAnalysisResult.NOT_RESOURCED));
		s.append("\t No resource units: \t" + resultLine(TaskAnalysisResult.ZERO_ASSIGNMENT));
		s.append("\t Empty resource group: \t" + resultLine(TaskAnalysisResult.RESOURCE_GROUP_EMPTY));
		s.append("\t Not planned: \t\t" + resultLine(TaskAnalysisResult.NOT_PLANNED));
		s.append("\t Plan date too early: \t" + resultLine(TaskAnalysisResult.PLANNED_TOO_EARLY));

	}

	private String resultLine(TaskAnalysisResult tar) {
		return results.get(tar)
				.size() + "\n";
	}

	private int resultCount(TaskAnalysisResult tar) {
		return results.get(tar)
				.size();

	}

	public int getResults(TaskAnalysisResult tar) {
		return resultCount(tar);
	}

	public List<Task> detail(TaskAnalysisResult tar) {
		return results.get(tar);
	}
}
